In [1]:
from blaseball_mike.models import Team
from blaseball_mike.chronicler import BASE_URL as CHRONICLER_URL
from blaseball_mike.reference import BASE_URL as DATABLASE_URL
import requests
import pandas
from plotly.graph_objects import Figure
import plotly.io as _pio
_pio.renderers.default = "notebook_connected"
In [2]:
# Make a big table of team average stars in all categories over time
INVALID_TEAMS = {"Hall Stars": "c6c01051-cdd4-47d6-8a98-bb5b754f937f",
                 "The Shelled One's Pods": "40b9ec2a-cb43-4dbb-b836-5accb62e7c20",
                 "Real Game Band": "7fcb63bc-11f2-40b9-b465-f1d458692a63",
                 "FWXBC": "e3f90fa1-0bbe-40df-88ce-578d0723a23b",
                 "Club de Calf": "a3ea6358-ce03-4f23-85f9-deb38cb81b20",
                 "BC Noir": "f29d6e60-8fce-4ac6-8bc2-b5e3cabc5696",
                 "Atlético Latte": "49181b72-7f1c-4f1c-929f-928d763ad7fb",
                 "Cold Brew Crew": "4d921519-410b-41e2-882e-9726a4e54a6a",
                 "Royal PoS": "9a5ab308-41f2-4889-a3c3-733b9aab806e",
                 "Cream & Sugar United": "b3b9636a-f88a-47dc-a91d-86ecc79f9934",
                 "Pandemonium Artists": "3b0a289b-aebd-493c-bc11-96793e7216d5",
                 "Society Data Witches": "d2634113-b650-47b9-ad95-673f8e28e687",
                 "Inter Xpresso": "d8f82163-2e74-496b-8e4b-2ab35b2d3ff1",
                 "Milk Proxy Society": "a7592bd7-1d3c-4ffb-8b3a-0b1e4bc321fd",
                 "Macchiato City": "9e42c12a-7561-42a2-b2d0-7cf81a817a5e",
                 "Light & Sweet Electric Co.": "70eab4ab-6cb1-41e7-ac8b-1050ee12eecc",
                 "Americano Water Works": "4e5d0063-73b4-440a-b2d1-214a7345cf16",
                 "Heavy FC": "e8f7ffee-ec53-4fe0-8e87-ea8ff1d0b4a9"}

def parse_emoji(val):
    try:
        return chr(int(val, 16))
    except ValueError:
        return val

# Get all season/days and their real-life timestamps
times = requests.get(f'{CHRONICLER_URL}/time/map').json()
times = list(filter(lambda x: x['type'] in ('season', 'postseason'), times["data"]))

# Get a list of all the teams we care about
teams = Team.load_all().values()
teams = [x for x in teams if x.id not in INVALID_TEAMS.values()]

# Pull the team info for each timestamp & calculate average stars
try:
    table = pandas.read_csv("team_star_changes.csv", index_col=0)
except FileNotFoundError:
    table = pandas.DataFrame()

for element in times:
    # If season/day already exists in the table, dont do it again
    if table.size != 0:
        existing_data = table[table["season"] == element["season"]+1]
        existing_data = existing_data[existing_data["day"] == element["day"]+1]
        if existing_data.size > 0:
            continue
    
    data = requests.get(f"{DATABLASE_URL}/allPlayersForGameday?season={element['season']}&day={element['day']}").json()
    temp_table = pandas.DataFrame(data)

    for team in teams:
        # Filter out players on this team
        players = temp_table[temp_table["team_id"] == team.id]
        if players.size <= 0:
            bat_avg = 0.0
            pitch_avg = 0.0
            base_avg = 0.0
            def_avg = 0.0
        else:
            lineup = players[players["position_type"] == "BATTER"]
            rotation = players[players["position_type"] == "PITCHER"]
            lineup_no_shelled = lineup[~lineup["modifications"].astype(str).str.contains("SHELLED")]
            rotation_no_shelled = rotation[~rotation["modifications"].astype(str).str.contains("SHELLED")]

            bat_avg = lineup_no_shelled["batting_stars"].astype(float).mean()
            pitch_avg = rotation_no_shelled["pitching_stars"].astype(float).mean()
            base_avg = lineup_no_shelled["baserunning_stars"].astype(float).mean()
            def_avg = lineup["defense_stars"].astype(float).mean()

        table = table.append({"team": team.nickname, "emoji": parse_emoji(team.emoji), "color": team.main_color,
                              "batting": bat_avg, "pitching": pitch_avg, "baserunning": base_avg, "defense": def_avg,
                              "day": element["day"]+1, "season": element["season"]+1}, ignore_index=True)

    # Incrementally save the result to a CSV
    table.to_csv("team_star_changes.csv", na_rep="0.0")
In [3]:
# Plot it
fig_dict = {
    "data": [],
    "layout": {},
    "frames": []
}

# Make the graph layout & fancy play/pause buttons
fig_dict["layout"]["title"] = "Average Stars over Time"
fig_dict["layout"]["xaxis"] = {"range": [1, 4.5], "title": "Batting Stars"}
fig_dict["layout"]["yaxis"] = {"range": [0, 4.5], "title": "Pitching Stars"}
fig_dict["layout"]["hovermode"] = "closest"
fig_dict["layout"]["updatemenus"] = [
    {
        "buttons": [
            {
                "args": [None, {"frame": {"duration": 250, "redraw": False},
                                "fromcurrent": True, "transition": {"duration": 250, "easing": "quadratic-in-out"}}],
                "label": "▶",
                "method": "animate"
            },
            {
                "args": [[None], {"frame": {"duration": 0, "redraw": False},
                                  "mode": "immediate", "transition": {"duration": 0}}],
                "label": "⏸",
                "method": "animate"
            }
        ],
        "direction": "left",
        "pad": {"r": 10, "t": 87},
        "showactive": False,
        "type": "buttons",
        "x": 0.1,
        "xanchor": "right",
        "y": 0,
        "yanchor": "top"
    }
]

sliders_dict = {
    "active": 0,
    "yanchor": "top",
    "xanchor": "left",
    "currentvalue": {
        "font": {"size": 14},
        "visible": True,
        "xanchor": "right"
    },
    "transition": {"duration": 250, "easing": "cubic-in-out"},
    "pad": {"b": 10, "t": 50},
    "len": 0.9,
    "x": 0.1,
    "y": 0,
    "steps": []
}

# Add data
first = True
for season in table["season"].unique():
    subset = table[table["season"] == season]
    for day in subset["day"].unique():
        frame = {"data": [], "name": f"Season {int(season)}, Day {int(day)}"}
        dayset = subset[subset["day"] == day]
        for team in dayset["team"].unique():
            teamset = dayset[dayset["team"] == team]
            data_dict = {
                "x": list(teamset["batting"]),
                "y": list(teamset["pitching"]),
                "mode": "markers+text",
                "marker": {
                    "color": teamset["color"],
                    "size": 36,
                    "opacity": 1,
                },
                "name": team,
                "text": teamset["emoji"],
                "textposition": "middle center",
                "textfont_size": 20
            }
            
            if first:
                fig_dict["data"].append(data_dict)
            frame["data"].append(data_dict)
            
        fig_dict["frames"].append(frame)
        
        slider_step = {
            "args": [
                [frame["name"]],
                {
                    "frame": {"duration": 250, "redraw": False},
                    "mode": "immediate",
                    "transition": {"duration": 250}
                }
            ],
            "label": frame["name"],
            "method": "animate"
        }
        sliders_dict["steps"].append(slider_step)
        first = False

    
fig_dict["layout"]["sliders"] = [sliders_dict]

fig = Figure(fig_dict)
fig.show()
In [4]:
# Do Defense & Baserunning too

fig_dict["layout"]["xaxis"] = {"range": [1.5, 4.5], "title": "Defense Stars"}
fig_dict["layout"]["yaxis"] = {"range": [1, 5], "title": "Baserunning Stars"}
fig_dict["data"] = []
fig_dict["frames"] = []
sliders_dict["steps"] = []

first = True
for season in table["season"].unique():
    subset = table[table["season"] == season]
    for day in subset["day"].unique():
        frame = {"data": [], "name": f"Season {int(season)}, Day {int(day)}"}
        dayset = subset[subset["day"] == day]
        for team in dayset["team"].unique():
            teamset = dayset[dayset["team"] == team]
            data_dict = {
                "x": list(teamset["defense"]),
                "y": list(teamset["baserunning"]),
                "mode": "markers+text",
                "marker": {
                    "color": teamset["color"],
                    "size": 36,
                    "opacity": 1,
                },
                "name": team,
                "text": teamset["emoji"],
                "textposition": "middle center",
                "textfont_size": 20
            }
            
            if first:
                fig_dict["data"].append(data_dict)
            frame["data"].append(data_dict)
            
        fig_dict["frames"].append(frame)
        
        slider_step = {
            "args": [
                [frame["name"]],
                {
                    "frame": {"duration": 250, "redraw": False},
                    "mode": "immediate",
                    "transition": {"duration": 250}
                }
            ],
            "label": frame["name"],
            "method": "animate"
        }
        sliders_dict["steps"].append(slider_step)
        first = False
        
fig_dict["layout"]["sliders"] = [sliders_dict]

fig = Figure(fig_dict)
fig.show()